package br.ufrj.ad;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import br.ufrj.ad.configuracao.Cenario;
import br.ufrj.ad.configuracao.Parametros;
import br.ufrj.ad.entidade.Peer;
import br.ufrj.ad.entidade.Publisher;
import br.ufrj.ad.entidade.Rodada;
import br.ufrj.ad.estatistica.Amostragem;
import br.ufrj.ad.estatistica.Medida;
import br.ufrj.ad.evento.Chegada;
import br.ufrj.ad.evento.Evento;
import br.ufrj.ad.evento.PeerUpload;
import br.ufrj.ad.evento.PublisherUpload;

public class Simulacao {
	private static final Logger log = Logger.getLogger(Simulacao.class);
/*	
	public static double tempo = 0;
	public static PriorityQueue<Evento> eventos = new PriorityQueue<Evento>();
	public static Parametros parametros;
	public static List<Peer> peers = new ArrayList<Peer>();
	public static List<Seed> seeds = new ArrayList<Seed>();
	
	public static ArrayList<Bloco> frequenciaBlocos = new ArrayList<Bloco>();
	private static ArrayList<Rodada> rodadas = new ArrayList<Rodada>();
	
*/	
	//Criando essa classe pra substituir as coisas estáticas
	private As as;
	
	static int EVENTOS_RODADA;// = 15000;
	static int EVENTOS_FASE_TRANSIENTE;// = 10000;
	static int MINIMO_RODADAS;// = 15;
	
	public static TreeMap<Integer, Double> vazaoMedia = new TreeMap<Integer, Double>();
	public static TreeMap<Integer, Double> vazaoIC = new TreeMap<Integer, Double>();
	
	public Simulacao(Parametros parametros) {
		super();
		this.as = new As(parametros);
	}

	private void inicializa(Rodada r, int populacaoInicial) {
		if(populacaoInicial == 0) {
			//População aberta
			as.eventos.add(new Chegada(as.tempo + Amostragem.exponencial(as.parametros.LAMBDA), new Peer(as), r, as));
		} else {
			//População fechada
			for (int i = 0; i < populacaoInicial; i++) {
				Peer peer = new Peer(as);
				peer.setTempoChegada(as.tempo);
				PeerUpload event = new PeerUpload(as.tempo+Amostragem.exponencial(as.parametros.MI), peer, r, as);
				peer.addUploadAgendado(event);
				as.eventos.add(event);
				as.peers.add(peer);
			}
		}
		
		as.eventos.add(new PublisherUpload(as.tempo + Amostragem.exponencial(as.parametros.U) , new Publisher(as), r, as));
	}
	
	public void roda() {
		
		log.info("inicio"); 
		double duracao = System.currentTimeMillis();
		
		Rodada r = new Rodada(-1, as.tempo );

		//inicializando
		inicializa(r, as.parametros.N0);
		
		//--------------Fase transiente-----------------------------
		double inicio = as.tempo;
		for (int i = 0; i < EVENTOS_FASE_TRANSIENTE; i++) {
			
			Evento e = as.eventos.poll();
			e.processaEvento(r);
			as.tempo = e.getTempo();
			
		}
		log.info("(X) rodada transiente terminou. (inicio="+inicio+" , fim="+as.tempo +" , duracao="+(as.tempo - inicio)+")" );
		r.setFim(as.tempo);
		as.rodadas.add(r);
		
		//-------------Rodadas normais-------------------------------
		for (int numRodada = 0; !Medida.is_IC95_Satisfeito(as.rodadas, MINIMO_RODADAS) ; numRodada++) {
			Rodada rodada = new Rodada(numRodada, as.tempo);
			inicio = as.tempo;
			for (int i = 0; i < EVENTOS_RODADA; i++) {
				Evento e = as.eventos.poll();
				e.processaEvento(rodada);
				as.tempo = e.getTempo();
			}
			log.info("("+as.parametros.cenario+","+as.parametros.N0+") rodada "+numRodada+" terminou. (inicio="+inicio+" , fim="+as.tempo +" , duracao="+(as.tempo - inicio)+")");
			rodada.setFim(as.tempo);
			as.rodadas.add(rodada);
		}
		
		//----------------- rodando a MM1 -------------------------
		if(as.parametros.cenario == 11 || as.parametros.cenario == 12 || as.parametros.cenario == 13) {
			
			as.parametros = Cenario.getCenario(-as.parametros.cenario);
			Rodada rodadamm1 = new Rodada(-2, as.tempo);
			inicio = as.tempo;
			for (int i = 0; i < EVENTOS_RODADA; i++ ) {
				Evento e = as.eventos.poll();
	 			e.processaEvento(rodadamm1);
				as.tempo = e.getTempo();
			}
			log.info("("+as.parametros.N0+") rodada M/M/1 terminou. (inicio="+inicio+" , fim="+as.tempo +" , duracao="+(as.tempo - inicio)+")");
			rodadamm1.setFim(as.tempo);
			as.rodadas.add(rodadamm1);
			
			as.parametros = Cenario.getCenario(-as.parametros.cenario);
		}
		
		//------------------ IMPRIME ----------------------------
		try {
			imprimeTraces();
		} catch (IOException e) {
			log.error("erro IO!",e);
			e.printStackTrace();
		}
		
		double fim = System.currentTimeMillis();
		duracao = fim - duracao;
		duracao = duracao / 1000;
		log.info("(!) fim ( "+duracao+"s )");
	}

	private void imprimeTraces() throws IOException {
		
		int cenario = as.parametros.cenario;
		String pasta = "./target/cenario"+(cenario/10)+"/";
		String extensao = ".csv";
		DecimalFormat df = new DecimalFormat("0.00000000000000000000");// 20 casas decimais
		log.setLevel(Level.INFO);
		
		Appender fa = null;
	
		if(cenario < 30) {
			
		//---------------GRAFICO pmf MM1-----------------------
		fa = new FileAppender( new PatternLayout("%25m%n"), pasta + "pmf_"+cenario+"_"+as.parametros.N0+extensao, false);
		log.addAppender(fa);
		
		log.info("Pergunta;Cenario;Rodada;EixoX;EixoY");
		Collections.sort(as.rodadas);
		
		if( cenario == 11 || cenario == 12 || cenario == 13 ) {
			Rodada rodadaMM1 = as.rodadas.get(0);
			as.rodadas.remove(rodadaMM1);
			for (Map.Entry<Integer, Double> entry : rodadaMM1.getTempoTamanhoPopulacao().entrySet()) {
				String probabilidade = df.format( entry.getValue()/(rodadaMM1.getDuracao()) );
				log.info("3.1.1;"+cenario+";mm1;"+entry.getKey()+";"+probabilidade);
			}
			rodadaMM1 = null; //nao usar mais!
		}
		
		//rodada transiente
		Rodada rodadaTransiente = as.rodadas.get(0);
		as.rodadas.remove(rodadaTransiente);
		rodadaTransiente = null;// nao usar!
		
		// -------------------GRAFICO pmf --------------------------------
		DescriptiveStatistics tempoDownloads = new DescriptiveStatistics();
		TreeMap<Integer, DescriptiveStatistics> icpmf = new TreeMap<Integer, DescriptiveStatistics>();
		
		for (Rodada rodada : as.rodadas) {
			for (Map.Entry<Integer, Double> entry : rodada.getTempoTamanhoPopulacao().entrySet()) {
				double probabilidade = entry.getValue()/(rodada.getDuracao());
				String probabilidadeString = df.format( probabilidade );
				log.info("3.1.1;"+cenario+";rodada"+rodada.getNumeroRodada()+";"+entry.getKey()+";"+probabilidadeString);
				tempoDownloads.addValue(rodada.getMediaTempoDownloads());
				
				if(icpmf.get(entry.getKey()) == null) {
					DescriptiveStatistics probs = new DescriptiveStatistics();
					probs.addValue(probabilidade);
					icpmf.put(entry.getKey(), probs);
				} else {
					icpmf.get(entry.getKey()).addValue(probabilidade);
				}
			}
		}
		//Imprime IC inferior
		for (Map.Entry<Integer, DescriptiveStatistics> entry : icpmf.entrySet()) {
			double media = entry.getValue().getMean();
			double ic = Medida.IC95(entry.getValue().getVariance(), entry.getValue().getN());
			double inferior = media - ic;
			log.info("3.1.1;"+cenario+";icInferior;"+entry.getKey()+";"+df.format(inferior));
		}
		//Imprime IC superior
		for (Map.Entry<Integer, DescriptiveStatistics> entry : icpmf.entrySet()) {
			double media = entry.getValue().getMean();
			double ic = Medida.IC95(entry.getValue().getVariance(), entry.getValue().getN());
			double superior = media + ic;
			log.info("3.1.1;"+cenario+";icSuperior;"+entry.getKey()+";"+df.format(superior));
		}
		
		log.removeAppender(fa);
		
		//  -------------------TABELA 3 ----------------------------------
		fa = new FileAppender( new PatternLayout("%25m%n"), pasta + "tabela3_"+cenario+extensao, false);
		log.addAppender(fa);
		log.info("Pergunta;Cenario;Media;IC inferior;IC superior");
		//df = new DecimalFormat("0.00000000000000000000");
		double media = tempoDownloads.getMean();
		double inferior = media - Medida.IC95(tempoDownloads.getVariance(), tempoDownloads.getN());
		double superior = media + Medida.IC95(tempoDownloads.getVariance(), tempoDownloads.getN());
		log.info("3.1.2a);"+cenario+";"+df.format(media)+";"+df.format(inferior)+";"+df.format(superior)+";");
		
		log.removeAppender(fa);
		
		// --------------------GRAFICO pmf -----------------------------------
		fa = new FileAppender( new PatternLayout("%25m%n"), pasta + "cdf_"+cenario+"_"+as.parametros.N0+extensao, false);
		log.addAppender(fa);
		
		log.info("Pergunta;Cenario;Rodada;EixoX;EixoY;");
		Rodada dummy = new Rodada(0,0);
		for (Rodada rodada : as.rodadas) {
			for(int x = 1; x <= 100; x++) {
				double prob = rodada.getPercentil(new Double(x));
				log.info("3.1.2b);"+cenario+";rodada"+rodada.getNumeroRodada()+";"+x+";"+df.format(prob)+";");
			}
			dummy.addTempoDownload(0, rodada.getMediana());
		}
		log.removeAppender(fa);
		
		// -------------------- TABELA 4 -----------------------------------------
		fa = new FileAppender( new PatternLayout("%25m%n"), pasta + "tabela4_"+cenario+extensao, false);
		log.addAppender(fa);
		log.info("Pergunta;Cenario;Media;IC inferior;IC superior");
		double mediana = dummy.getMediana();
		double icinferior = mediana - Medida.IC95(dummy.getStatistica().getVariance(), dummy.getStatistica().getN());
		double icsuperior = mediana + Medida.IC95(dummy.getStatistica().getVariance(), dummy.getStatistica().getN());
		log.info("3.1.2c);"+cenario+";"+df.format(mediana)+";"+df.format(icinferior)+";"+df.format(icsuperior)+";");
		
		log.removeAppender(fa);
		} else if (cenario >= 30) {
			
		
		//---------------------VAZAO ------------------------------------------------
		//fa = new FileAppender( new PatternLayout("%25m%n"), pasta + "vazao_"+cenario+extensao, false);
		DescriptiveStatistics estVazao = new DescriptiveStatistics();
		for (Rodada rodada : as.rodadas) {
			double vazao = rodada.getSaidas() / rodada.getDuracao();
			estVazao.addValue(vazao);
		}
		vazaoMedia.put(as.parametros.N0, estVazao.getMean() );
		vazaoIC.put(as.parametros.N0, Medida.IC95(estVazao.getVariance(), estVazao.getN()) );
		}
		
		
	}

	public static void imprimeTracesVazao(int cenario, int rodadas) throws IOException {
		
		String pasta = "./target/cenario"+(cenario/10)+"/";
		String extensao = ".csv";
		DecimalFormat df = new DecimalFormat("#0.00000000000000000000");// 20 casas decimais
		log.setLevel(Level.INFO);
		
		Appender fa = null;
			
		//---------------GRAFICO VAZAO-----------------------
		fa = new FileAppender( new PatternLayout("%25m%n"), pasta + "vazao_"+cenario+extensao, false);
		log.addAppender(fa);
		
		log.info("Pergunta;N0;Media;IC inferior;IC superior");
		for (int i = 1; i <= rodadas; i++) {
			log.info("3.1.3;"+i+";"+df.format(vazaoMedia.get(i))+";"+df.format(vazaoMedia.get(i) - vazaoIC.get(i))+";"+df.format(vazaoMedia.get(i) + vazaoIC.get(i))+";");
		}
		log.removeAppender(fa);
	}
	
	/*
		//Exemplo de como funciona a lista dos blocos!
		ArrayList<Bloco> l = new ArrayList<Bloco>();
		l.add(new Bloco(4, 1));
		l.add(new Bloco(3, 8));
		l.add(new Bloco(2, 100));
		l.add(new Bloco(1, 4));
		l.add(new Bloco(5, 4));
		l.add(new Bloco(6));
		Collections.sort(l);
		
		System.out.println(l.get(0));
		System.out.println(l.get(l.size()-1));
		System.out.println(l.get(l.indexOf(new Bloco(1))));
		l.get(l.indexOf(new Bloco(1))).aumentaFrequencia();
		System.out.println(l.get(l.indexOf(new Bloco(1))));	*/
}
